//
// Unihan Input Method
//
// Copyright © 2002-2007 Sun Wah Linux Ltd.
//
//
// This library is free software; you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation; either version 2.1 of the
// License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
// USA
//
// $Id: bs2tab.c 757 2007-05-10 08:19:31Z roger $
//

#ifdef HAVE_CONFIG_H
# include <config.h>
#endif

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <getopt.h>
#include <iconv.h>
#include <errno.h>

#include <SWTable.h>

typedef struct _ConvModel {
	char *bsfilename;
	FILE *bsfile;
	char *tabfilename;
	FILE *tabfile;
	char *adjusterfilename;
	FILE *adjusterfile;
	char *encoding;
	Hints hints;
} ConvModel;

typedef struct _CharDef {
	char key[256];
	char value[256];
	struct _CharDef *prev;
	struct _CharDef *next;
} CharDef;

typedef struct _MaxFrequenceDef {
	int maxFreq;
	struct _MaxFrequenceDef *next;
} MaxFrequenceDef;

/* table body */
const char *exec_name;
char *table_sig = IM_TBL_SIG;
TableHeader table_header;
KeyNameTable key_name_table[0x80-0x20];
FastIdx fastidx[0x80-0x20];
CheckListIndex *check_list_index = NULL;
RevSearchTable *rstable = NULL;
MaxFrequence *maxfreq_table = NULL;
Frequence *freq_table = NULL;
CharStickTable *stick_table = NULL;

CharDef *chardef = NULL;
CharDef *chardef_end = NULL;
MaxFrequenceDef *maxfreq = NULL;
int num_max = 0;

CN_IDX max_char = 0;
CN_IDX min_char = 0;

iconv_t iconvd;
int need_iconv;

/* Function prototypes */
static int conv_init(int, char**, ConvModel *);
static void conv_done(ConvModel *);
static int parse_model(ConvModel *);
static void create_idx(void);
static void create_adjuster();
static int write_bs_table(FILE *);
static int write_adjuster_table(FILE *);

static void handle_ename(FILE *, char *);
static void handle_cname(FILE *, char *);
static void handle_keyname(FILE *, char *);
static void handle_chardef(FILE *, char *);
void print_usage(void);

static int
conv_init(int argc, char **argv, ConvModel *model)
{
	char c;
	char filename[256];
	char *p;
	size_t len;

#if DEBUG
	fprintf(stderr, "conv init ...\n");
#endif
	/* get program name */
	exec_name = strrchr(argv[0], '/');
	if (!exec_name)
		exec_name = argv[0];
	else
		++exec_name;

	if (argc < 2) {
		fprintf(stderr, "Error: need filename\n");
		print_usage();
		exit(1);
	}

	memset(filename, 0, sizeof(filename));
	strcpy(filename, argv[1]);
	if ((p = strstr(filename, FILE_SUFFIX_BS)) != NULL) {
		char *p = strstr(filename, FILE_SUFFIX_BS);
		*p = 0x0;
	}

	while ((c = getopt(argc, argv, "e:o:a:s")) != -1) {
		switch (c) {
			case 'e':
				model->encoding = strdup(optarg);
				break;
			case 'o':
				model->tabfilename = strdup(optarg);
				break;
			case 'a':
				model->adjusterfilename = strdup(optarg);
				break;
			case 's':
				model->hints |= SHOW_AS_U_GO;
				break;
			case '?':
				print_usage();
				exit(0);
				break;
		}
	}

	len = (size_t)(strlen(filename) + strlen(FILE_SUFFIX_BS) + 1);
	model->bsfilename = (char *)malloc(len);
	memset(model->bsfilename, 0, len);
	sprintf(model->bsfilename, "%s%s", filename, FILE_SUFFIX_BS);

	model->bsfile = fopen(model->bsfilename, "r");
	if (!model->bsfile) {
		fprintf(stderr, "Error: cannot open %s\n", model->bsfilename);
		return FALSE;
	}

	if (!model->tabfilename) {
		len = (size_t)(strlen(filename) + strlen(FILE_SUFFIX_TAB) + 1);
		model->tabfilename = (char *)malloc(len);
		memset(model->tabfilename , 0, len);
		sprintf(model->tabfilename, "%s%s", filename, FILE_SUFFIX_TAB);
	}

	if (!model->adjusterfilename) {
		len = (size_t)(strlen(filename) + strlen(FILE_SUFFIX_ADJ) + 1);
		model->adjusterfilename = (char *)malloc(len);
		memset(model->adjusterfilename , 0, len);
		sprintf(model->adjusterfilename, "%s%s", filename, FILE_SUFFIX_ADJ);
	}


	model->tabfile = fopen(model->tabfilename, "w");
	if (!model->tabfile) {
		fprintf(stderr, "Error: cannot write to %s\n", model->tabfilename);
		return FALSE;
	}

	model->adjusterfile = fopen(model->adjusterfilename, "w");
	if (!model->adjusterfile) {
		fprintf(stderr, "Error: cannot write to %s\n", model->adjusterfilename);
		return FALSE;
	}

	if (!model->encoding) {
		//fprintf(stderr, "iconv : BIG5HKSCS to UTF 8 ... first\n");
		//iconvd = iconv_open("UTF-8", "BIG5HKSCS");
		fprintf(stderr, "iconv : UTF-8 to UTF 8 ... first\n");
		iconvd = iconv_open("UTF-8", "UTF-8");
	} else {
		if (!strcasecmp(model->encoding, "GB2312")) {
			iconvd = iconv_open("UTF-8", "GB2312");
			fprintf(stderr, "iconv : GB2312 to UTF-8\n");
		}
		else if (!strcasecmp(model->encoding, "BIG5")
			|| !strcasecmp(model->encoding, "BIG5HKSCS")) {
			iconvd = iconv_open("UTF-8", "BIG5HKSCS");
			fprintf(stderr, "iconv : BIG5HKSCS to UTF-8 ... second\n");
		}
		else {
			iconvd = iconv_open("UTF-8", "UTF-8");
			need_iconv = 0;
			fprintf(stderr, "iconv : no converting\n");
	        }
	}
	return TRUE;
}

static void
conv_done(ConvModel *model)
{
#if DEBUG
	fprintf(stderr, "conv done ... ...\n");
#endif
	if (model->bsfilename)
		free(model->bsfilename);
	if (model->bsfile)
		fclose(model->bsfile);
	if (model->tabfilename)
		free(model->tabfilename);
	if (model->tabfile)
		fclose(model->tabfile);
	if (model->adjusterfilename)
		free(model->adjusterfilename);
	if (model->adjusterfile)
		fclose(model->adjusterfile);
	if (model->encoding)
		free(model->encoding);
}

static void
handle_ename(FILE *bsfile, char *arg)
{
#if DEBUG
	fprintf(stderr, "handle ename %s... ...\n", arg);
#endif
	strcpy(table_header.ename, arg);
}

static void
handle_cname(FILE *bsfile, char *arg)
{
	char *tmpcnamesave, *tmpcname = NULL;
	int outbytesleft = 64;
	int inbytesleft = strlen(arg);

#if DEBUG
	fprintf(stderr, "handle cname %s... ...\n", arg);
#endif
	tmpcnamesave = tmpcname = (char *)malloc(sizeof(char)*64);

	if (need_iconv) {
		iconv(iconvd, &arg, &inbytesleft, &tmpcname, &outbytesleft);
		tmpcnamesave[outbytesleft] = 0;
		strcpy(table_header.cname, tmpcnamesave);
	} else {
		strcpy(table_header.cname, arg);
	}
	free(tmpcnamesave);
}

void
handle_selkey(FILE *bsfile, char *arg)
{
#if DEBUG
	fprintf(stderr, "handle selkey ... ...\n");
#endif
	return;
}

static void 
handle_keyname(FILE *bsfile, char *arg)
{
	char key[64], *namesave, *name = NULL;
	char *tmpnamesave, *tmpname = NULL;
	int inbytesleft;
	int outbytesleft = 64;

#if DEBUG
	fprintf(stderr, "handle keyname ... ...\n");
#endif
	if (strcmp("begin", arg)!=0) {
		printf("parse error\n");
		return;
	}

	memset(key_name_table, 0, (0x80-0x20)*sizeof(KeyNameTable));

	namesave = name = (char *)malloc(sizeof(char)*64);
	memset(name, 0, sizeof(char)*64);

	while (fscanf(bsfile, "%s %s", key, namesave) == 2) {
		if (!strcmp("%keyname", key) &&
		    !strcmp("end", namesave))
			break;

		if (strlen(namesave) == 0)
			continue;

		if (need_iconv) {
			name = namesave;
			inbytesleft = strlen(name);
			tmpnamesave = tmpname = (char *)malloc(sizeof(char)*64);
			memset(tmpname, 0, 64);
			outbytesleft = 64;
			errno = 0;
			iconv(iconvd, &name, &inbytesleft, &tmpname, &outbytesleft);
			if (errno) {
				printf("error converting to UTF-8! ");
				printf("(%s %s)\n", key, namesave);
				return;
			}

			tmpnamesave[6] = 0;
			strcpy(key_name_table[idx(key[0])].name, tmpnamesave);
			free(tmpnamesave);
		} else {
			strncpy(key_name_table[idx(key[0])].name, namesave, 7);
		}
		memset(namesave, 0, sizeof(char)*64);
	}

	free(namesave);
	return;
}

static void
handle_chardef(FILE *bsfile, char *arg)
{
	char *key = NULL, *valuesave = NULL, *value = NULL;
	char *tmpvalsave = NULL, *tmpval = NULL;
	int i, inbytesleft, outbytesleft;
	CharDef *newcd;
	int keyused[0x60];

#if DEBUG
	fprintf(stderr, "handle_chardef ... ...\n");
#endif
	if (strcmp("begin", arg)!=0) {
		printf("parse error\n");
		return;
	}

	memset(keyused, 0, sizeof(int)*0x60);

	key = (char*)malloc(sizeof(char)*256);
	valuesave = value = (char*)malloc(sizeof(char)*256);
	tmpvalsave = tmpval = (char*)malloc(sizeof(char)*256);

	memset(key, 0, 256);
	memset(value, 0, 256);

	while (fscanf(bsfile, "%s %[^\n]", key, valuesave) == 2) {
		if (!strcmp("%chardef", key) &&
		    !strcmp("end", valuesave))
			break;

		value = valuesave;
		if (strlen(value) == 0)
			continue;

		tmpval = tmpvalsave;
		if (need_iconv) {
			inbytesleft = strlen(value);
			memset(tmpval, 0, 256);
			outbytesleft = 256;
			errno = 0;
			iconv(iconvd, &value, &inbytesleft, &tmpval, &outbytesleft);
			if (errno) {
				printf("error converting to UTF-8! ");
				printf("(%s %s)\n", key, valuesave);
				//free(tmpvalsave);
				continue;
			}
		} else {
			strcpy(tmpvalsave, value);
		}
		tmpvalsave[255] = 0; newcd = (CharDef*)malloc(sizeof(CharDef));
		memset(newcd, 0, sizeof(CharDef));
		strcpy(newcd->key, key);
		strcpy(newcd->value, tmpvalsave);
		if (!chardef) {
			chardef = newcd;
			chardef_end = newcd;
			newcd->prev = NULL;
			newcd->next = NULL;
			UTF_CN_TO_INT_IDX(max_char, newcd->value);
			UTF_CN_TO_INT_IDX(min_char, newcd->value);
		} else {
			CharDef *cd = chardef_end;
			CharDef *cd_next = NULL;
			CN_IDX ch_idx;
		
			/* insert newcd following dictionary sort */ 
			while (cd) {
				if (strcmp(key, cd->key) >= 0)
					break;
				cd_next = cd;
				cd = cd->prev;
			}

			newcd->prev = cd;
			if (cd) {
				cd->next = newcd;
			} else {
				chardef = newcd;
			}
			newcd->next = cd_next;
			if (cd_next) {
				cd_next->prev = newcd;
			} else {
				chardef_end = newcd;
			}
			UTF_CN_TO_INT_IDX(ch_idx, newcd->value);
			if (ch_idx > max_char)
				max_char = ch_idx;
			else if (ch_idx < min_char)
				min_char = ch_idx;
			/* **************************************** */
		}

		for (i = 0; i < strlen(key); i++) {
			if ((unsigned char)key[i] < 0x20 ||
			    (unsigned char)key[i] >= 0x80)
				continue;
			keyused[idx(key[i])] = 1;
		}

		table_header.numChar++;

		table_header.charTableLen += strlen(tmpvalsave) + 1;

		//free(tmpvalsave);
	}
	table_header.minChar = min_char;
	table_header.numRevChar = max_char - min_char;
#if DEBUG
	fprintf(stderr, "get max char : %d\n", max_char);
	fprintf(stderr, "get mini char : %d\n", min_char);
	fprintf(stderr, "get total chars : %d\n", table_header.numRevChar);
#endif

	free(tmpvalsave);
	free(valuesave);
	free(key);

	return;
}

static void
create_adjuster()
{
	CharDef *chardef_end = chardef->next;
	MaxFrequenceDef *freq_end = maxfreq;
	int num_val_last, num_val_current;

#if DEBUG
	fprintf(stderr, "create adjuster ... ...\n");
#endif
	num_val_last = 0;
	num_val_current = 1;

	while(chardef_end) {
		num_val_current++;
		if (strcmp (chardef_end->key, chardef_end->prev->key) != 0) {
			if (!maxfreq) {
				maxfreq = (MaxFrequenceDef *)malloc(
						sizeof(MaxFrequenceDef));
				if (maxfreq) {
					maxfreq->maxFreq = (num_val_current - 1) * FREQ_CELL;
					maxfreq->next = NULL;
				}
				freq_end = maxfreq;
				num_val_last = num_val_current;
				/* equare to (num_val_last = num_val - 1) */
			}
			else {
				freq_end->next = (MaxFrequenceDef *)malloc(sizeof(MaxFrequenceDef));
				if (freq_end->next) {
					freq_end->next->maxFreq = (num_val_current - num_val_last) * FREQ_CELL;
					/* equare to (num_val - 1 - num_val_last) */
					freq_end = freq_end->next;
					freq_end->next = NULL;
					num_val_last = num_val_current;
				}
			}
		}
		chardef_end = chardef_end->next;
	}

	/* I don't think there will be only one key defined */
	freq_end->next = (MaxFrequenceDef *)malloc(sizeof(MaxFrequenceDef));
	if (freq_end->next) {
		freq_end->next->maxFreq = (num_val_current - num_val_last + 1) * FREQ_CELL;
		freq_end = freq_end->next;
		freq_end->next = NULL;
	}
	freq_table = (Frequence *)malloc(table_header.numChar * sizeof(Frequence));
	memset(freq_table, 0, table_header.numChar * sizeof(Frequence));
}

static void
create_idx(void)
{
	int j, i = 0, m;
	int curr_keyoff = 0;
	int curr_charoff = 0;
	char *currkey, *lastkey = NULL;
	char maxkey;
	CharDef *cd = chardef;
	MaxFrequenceDef *mp = maxfreq;
	int firstidx;
	int maxoff = 0;

#if DEBUG
	fprintf(stderr, "create idx ... ...\n");
#endif
	check_list_index = (CheckListIndex *)malloc(sizeof(CheckListIndex) * table_header.numChar);
	memset(check_list_index, 0, sizeof(CheckListIndex) * table_header.numChar);
	rstable = (RevSearchTable *)malloc(sizeof(RevSearchTable) * (max_char - min_char));
	memset(rstable, 0, sizeof(RevSearchTable) * (max_char - min_char));
	stick_table = (CharStickTable *)malloc(sizeof(CharStickTable) * table_header.numChar);
	memset(stick_table, 0, sizeof(CharStickTable));

	memset(fastidx, 0, sizeof(FastIdx)*(0x80-0x20));

	/* chardef must be sorted */

	/* first key definition */
	currkey = cd->key;
	maxkey = currkey[0];
	firstidx = idx(currkey[0]);
	fastidx[idx(currkey[0])].offset = 0;
	check_list_index[0].keyOff = 0;
	check_list_index[0].charOff = 0;
	check_list_index[0].frqOff = 0;
	check_list_index[0].maxFrqOff = 0;
	check_list_index[0].stickOff = 0;
	for (m=0; m<STICK_SIZE; m++) {
		stick_table[0].prefixOff[m] = -1;
		stick_table[0].suffixOff[m] = -1;
	}

	table_header.numKey++;
	curr_charoff += strlen(cd->value) + 1;
	lastkey = cd->key;
	cd = cd->next;
	if (strcmp(currkey, lastkey) != 0)
		mp = mp->next;
	i++;
	/* ******************** */

	while (cd) {
		CN_IDX index;
		currkey = cd->key;

		if (currkey[0] != lastkey[0]) {
			/* update fastidx */
			fastidx[idx(currkey[0])].offset = i;
			for (j = idx(currkey[0])-1; j > firstidx; j--) {
				if (fastidx[j].offset != 0)
					break;
				fastidx[j].offset = i;
			}
			maxkey = currkey[0];
		}
		check_list_index[i].maxFrqOff = -1;

		if (strcmp(currkey, lastkey) != 0) {
			/* update curr_keyoff */
			curr_keyoff += strlen(lastkey) + 1;
			table_header.numKey++;
			if (mp->maxFreq > 1 * FREQ_CELL) {
				check_list_index[i].maxFrqOff = maxoff;
				maxoff++;
			}
			mp = mp->next;
		}
		else {
			if (mp->maxFreq > 1 * FREQ_CELL) {
				check_list_index[i].maxFrqOff = maxoff;
			}
		}

		check_list_index[i].keyOff = curr_keyoff;
		check_list_index[i].charOff = curr_charoff;
		check_list_index[i].frqOff = check_list_index[i-1].frqOff + 1;
		//check_list_index[i].stickOff = check_list_index[i-1].stickOff + 1;
		check_list_index[i].stickOff = i;
		for (m=0; m<STICK_SIZE; m++) {
			stick_table[i].prefixOff[m] = -1;
			stick_table[i].suffixOff[m] = -1;
		}
		curr_charoff += strlen(cd->value) + 1;

		UTF_CN_TO_INT_IDX(index, cd->value);
		rstable[index-min_char] = (RevSearchTable)i;

		lastkey = cd->key;
		cd = cd->next;
		i++;
	}
	fastidx[idx(maxkey+1)].offset = i;
	num_max = maxoff;

	table_header.keyTableLen = curr_keyoff + strlen(currkey) + 1;
}

static int
parse_model(ConvModel *model)
{
	char cmd[64], arg[64];
	char *argptr;
	int c;

#if DEBUG
	fprintf(stderr, "parse model ... ...\n");
#endif
	if (!model->bsfile)
		fprintf(stderr, "conv testing ... ...\n");
	if ((fscanf(model->bsfile, "%s ", cmd) != 1) ||
	    (strcmp("%gen_inp", cmd)!=0)) {
		fprintf(stderr, "Error: parse error\n");
		return FALSE;
	}

	while (!feof(model->bsfile)) {
		c = fgetc(model->bsfile);
		if (c == '#') {
			while (fgetc(model->bsfile) != '\n');
				continue;
		}
		ungetc(c, model->bsfile);
		if (fscanf(model->bsfile, "%s ", cmd) != 1) {
			if (!feof(model->bsfile))
				printf("cin parse error\n");
			break;
		}
		argptr = arg;
		c = fgetc(model->bsfile);
		if (c == '"') {
			while ((c = fgetc(model->bsfile)) && c != '"') {
				*argptr = c;
				argptr++;
			}
			*argptr = 0;
		} else {
			do {
				*argptr = c;
				argptr++;
			} while ((c = fgetc(model->bsfile)) && c != ' ' && c != '\n');
			*argptr = 0;
		}
		if (!strcmp("%ename", cmd))
			handle_ename(model->bsfile, arg);
		else if (!strcmp("%cname", cmd))
			handle_cname(model->bsfile, arg);
		else if (!strcmp("%keyname", cmd))
			handle_keyname(model->bsfile, arg);
		else if (!strcmp("%chardef", cmd))
			handle_chardef(model->bsfile, arg);
	}

	return TRUE;
}

static int
write_bs_table(FILE *tabfile)
{
	CharDef *cd = chardef;
	char *lastkey, *currkey;

#if DEBUG
	fprintf(stderr, "write base table ... ...\n");
#endif
	fwrite(IM_TBL_SIG, strlen(IM_TBL_SIG)+1, 1, tabfile);

	fwrite(&table_header, sizeof(TableHeader), 1, tabfile);

	fwrite(key_name_table, sizeof(KeyNameTable), 0x80-0x20, tabfile);

	fwrite(fastidx, sizeof(FastIdx), 0x80-0x20, tabfile);

	fwrite(check_list_index, sizeof(CheckListIndex), table_header.numChar, tabfile);
#if DEBUG
	fprintf(stderr, "write CheckListIndex total number : %d\n", table_header.numChar);
	fprintf(stderr, "sizeof CheckListIndex is %d\n", sizeof(CheckListIndex));
#endif

	lastkey = cd->key;
	fwrite(cd->key, strlen(cd->key)+1, 1, tabfile);
	while (cd) {
		currkey = cd->key;
		if (strcmp(lastkey, currkey) != 0) {
			lastkey = currkey;
			fwrite(currkey, strlen(currkey)+1, 1, tabfile);
		}
		cd = cd->next;
	}

	cd = chardef;
	while (cd) {
		fwrite(cd->value, strlen(cd->value)+1, 1, tabfile);
		cd = cd->next;
	}

	fwrite(rstable, sizeof(RevSearchTable), table_header.numRevChar, tabfile);
	return TRUE;
}

static int
write_adjuster_table(FILE *adjusterfile)
{
	int i;
	MaxFrequenceDef *mp = maxfreq;

#if DEBUG
	fprintf(stderr, "write adjuster table ... ...\n");
#endif
	fwrite(&num_max, sizeof(int), 1, adjusterfile);

	while (mp) {
		if (mp->maxFreq > 1 * FREQ_CELL) {
			fwrite(&(mp->maxFreq), sizeof(MaxFrequence), 1, adjusterfile);
		}
		mp = mp->next;
	}

	fwrite(freq_table, table_header.numChar * sizeof(Frequence), 1, adjusterfile);
	fwrite(stick_table, table_header.numChar * sizeof(CharStickTable), 1, adjusterfile);
	return TRUE;
}

void
print_usage(void)
{
	printf("Syntax: %s bsfile [-o tabfile] [-e encoding] [-s]\n\n", exec_name);
	printf("-o	Specify output file location\n");
	printf("-a	Specify adjuster file location\n");
	printf("-e	Allowed values: UTF-8, GB2312, BIG5HKSCS(default)\n");
	printf("-s	Define SHOW_AS_U_GO\n");
}

int
main(int argc, char *argv[])
{
	ConvModel conv_model;
	CharDef *pCD1, *pCD2;

	memset(&conv_model, 0, sizeof(ConvModel));
	memset(&table_header, 0, sizeof(TableHeader));
	if (!conv_init(argc, argv, &conv_model)) {
		conv_done(&conv_model);
		exit(1);
	}

	if (!parse_model(&conv_model)) {
		conv_done(&conv_model);
		exit(1);
	}
	create_adjuster();
	create_idx();

	write_bs_table(conv_model.tabfile);
	write_adjuster_table(conv_model.adjusterfile);

	free(check_list_index);
	free(rstable);
	free(maxfreq_table);
	free(freq_table);
	free(stick_table);
	pCD1 = chardef;
	while (pCD1) {
		pCD2 = pCD1->next;
		free(pCD1);
		pCD1 = pCD2;
	}
	conv_done(&conv_model);
	if (iconvd)
		iconv_close(iconvd);
	return 0;
}

// vi:ts=4:nowrap:cin:expandtab
